#include "HerzeleidPrecompiled.hxx"
#include "HerzeleidCoreWindow.hxx"
#include "HerzeleidRect.hxx"
#include "HerzeleidCoreDispatcher.hxx"
#include "HerzeleidCoreWindowEventArgs.hxx"

#define HerzeleidWindowClassName L"HerzeleidWindow"

using namespace HerzeleidUICoreNamespaceImpl;
using namespace HerzeleidSystemNamespaceImpl;

#define HERZELEID_USE_MOUSE_DATA (1)
#define HERZELEID_USE_POINTER_DATA (0)

#define _Herzeleid_use_default_window_style 1

CoreWindowPtr CoreWindow::s_pWindowForCurrentThread = nullptr;

CoreWindow::CoreWindow() : 
	m_PointerCursor(CoreCursorType::Arrow), // Default cursor type is arrow. 
	m_PointerPosition(Point::Empty()), // Default pointer position (unless the input is enabled)
	m_BoundingRect(Rect::Empty()), // Will be filled out when registering the window class 
	m_bIsInputEnabled(true), // Input is disabled unless another is stated. 
	m_bIsVisible(true), // Should be visible. 
	m_ModuleHandle(nullptr),
	m_Handle(nullptr) // No handle (assigned when registering the window)
{
	m_Handler = new CoreDispatcher(this); // Create window event dispatcher instance.
	s_pWindowForCurrentThread = this; //
}

LRESULT CALLBACK InternalWindowProc( 
	WindowHandle WindowHandle, UInt32 WindowMessage, 
	WParam WParam, LParam LParam 
	)
{
	//Char8 Buffer[16];
	//sprintf(Buffer, "%d\n", WindowMessage);
	//PrintToDebugWindowOnly8( "InternalWindowProc : Got message" );
	//PrintToDebugWindowOnly8( Buffer );

	auto pObj = GetWindowLongPtr( WindowHandle, 0 );

	if ( !pObj )
	{
		//PrintToDebugWindowOnly8( "InternalWindowProc : No handler" );
		return DefWindowProc( 
			WindowHandle, 
			WindowMessage,
			WParam, 
			LParam 
			);
	}
	else
	{
		auto pHandler = ((ICoreDispatcherMessageHandler*)pObj);
		//PrintToDebugWindowOnly8( "InternalWindowProc : Sending to handler" );
		return ((ICoreDispatcherMessageHandler*)pObj)->WindowProc( 
			WindowHandle, 
			WindowMessage, 
			WParam, 
			LParam
			);
	}
}

void CoreWindow::RegisterWindow()
{
	// Acquire raw app handle 

	if (!m_ModuleHandle) 
	{
		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Acquiring module handle" );
		m_ModuleHandle = GetModuleHandle(nullptr);
	}

	//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Creating window class" );
	// Render window class
	WNDCLASSEX WindowClass;

	// Set up the Window class
	ZeroMemory( &WindowClass, sizeof( WNDCLASSEX ) );
	WindowClass.cbSize = sizeof( WNDCLASSEX );
	WindowClass.hCursor = LoadCursor( null, IDC_ARROW );
	// long ptr size (win32 - long, x64 - long long)
	WindowClass.cbWndExtra = sizeof( ICoreDispatcherMessageHandler* );
	WindowClass.style = ( CS_HREDRAW | CS_VREDRAW | CS_OWNDC );
	WindowClass.hIcon = LoadIcon( null, IDI_WINLOGO );
	WindowClass.hIconSm = LoadIcon( null, IDI_WINLOGO );
	//WindowClass.hCursor = LoadCursor( null, IDC_ARROW );
	WindowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	WindowClass.lpszClassName = HerzeleidWindowClassName;
	WindowClass.lpfnWndProc = InternalWindowProc;
	WindowClass.lpszMenuName = null;
	WindowClass.hInstance = null;
	WindowClass.cbClsExtra = 0;

	switch (m_PointerCursor.Type)
	{
	case CoreCursorType::SizeNortheastSouthwest: WindowClass.hCursor = LoadCursor( null, IDC_SIZENESW ); break;
	case CoreCursorType::SizeNorthwestSoutheast: WindowClass.hCursor = LoadCursor( null, IDC_SIZENWSE ); break;
	case CoreCursorType::SizeNorthSouth: WindowClass.hCursor = LoadCursor( null, IDC_SIZENS ); break;
	case CoreCursorType::SizeWestEast: WindowClass.hCursor = LoadCursor( null, IDC_SIZEWE ); break;
	case CoreCursorType::UniversalNo: WindowClass.hCursor = LoadCursor( null, IDC_NO ); break;
	case CoreCursorType::UpArrow: WindowClass.hCursor = LoadCursor( null, IDC_UPARROW ); break;
	case CoreCursorType::SizeAll: WindowClass.hCursor = LoadCursor( null, IDC_SIZEALL ); break;
	case CoreCursorType::Custom: WindowClass.hCursor = LoadCursor( null, IDC_ARROW ); break;
	case CoreCursorType::Arrow: WindowClass.hCursor = LoadCursor( null, IDC_ARROW ); break;
	case CoreCursorType::Cross: WindowClass.hCursor = LoadCursor( null, IDC_CROSS ); break;
	case CoreCursorType::IBeam: WindowClass.hCursor = LoadCursor( null, IDC_IBEAM ); break;
	case CoreCursorType::Hand: WindowClass.hCursor = LoadCursor( null, IDC_HAND ); break;
	case CoreCursorType::Help: WindowClass.hCursor = LoadCursor( null, IDC_HELP ); break;
	case CoreCursorType::Wait: WindowClass.hCursor = LoadCursor( null, IDC_WAIT ); break;
	}

	//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Registering window class" );

	// Register Window Class
	AutoDebugVariable( Atom ) RegisterClassEx( 
		_In_ (&WindowClass) 
		);

	if ( m_bIsFullscreen )
	{

		PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Fullscreen" );

		m_BoundingRect.X = .0f;
		m_BoundingRect.Y = .0f;
		m_BoundingRect.Width = static_cast<Float32>(GetSystemMetrics(SM_CXSCREEN));
		m_BoundingRect.Height = static_cast<Float32>(GetSystemMetrics(SM_CXSCREEN));

		DEVMODEA dmScreenSettings;
		Standard::ZeroValue( &dmScreenSettings );
		dmScreenSettings.dmSize = sizeof(DEVMODE);
		dmScreenSettings.dmPelsWidth = (DWORD)(GetSystemMetrics(SM_CXSCREEN));
		dmScreenSettings.dmPelsHeight = (DWORD)(GetSystemMetrics(SM_CYSCREEN));
		dmScreenSettings.dmBitsPerPel = (DWORD)32;
		dmScreenSettings.dmFields = ( DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT );

		//Change the display settings to full screen.
		ChangeDisplaySettingsA(&dmScreenSettings, CDS_FULLSCREEN);
	}
	else
	{
		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Windowed" );
		/*m_BoundingRect.X = (static_cast<Float32>(GetSystemMetrics(SM_CXSCREEN)) - m_BoundingRect.Width) / 2.0f;
		m_BoundingRect.Y = (static_cast<Float32>(GetSystemMetrics(SM_CYSCREEN)) - m_BoundingRect.Height) / 2.0f;*/
		m_BoundingRect.X = 10.0f;
		m_BoundingRect.Y = 10.0f;
	}

	//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Creating window" );

	//Create the window with the screen settings and get the handle to it.
	m_Handle = CreateWindowEx(
		WS_EX_APPWINDOW, // window extended style
		WindowClass.lpszClassName, // window class name
		L"Herzeleid Demo", // window caption (invisible according to style) 
#if _Herzeleid_use_default_window_style
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, // window style for testing
#else
		WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP | WS_VISIBLE, // window style
#endif
		static_cast<Int32>( m_BoundingRect.X ), // left
		static_cast<Int32>( m_BoundingRect.Y ), // top
		static_cast<Int32>( m_BoundingRect.Width ), // width
		static_cast<Int32>( m_BoundingRect.Height ), // height
		NULL, // handle to parent
		NULL, // handle to menu
		m_ModuleHandle, // app instance
		NULL // extra params
		);

	/*TRACKMOUSEEVENT MouseTrackingConfig;
	Standard::ZeroValue( &MouseTrackingConfig );
	MouseTrackingConfig.cbSize = sizeof(TRACKMOUSEEVENT);
	MouseTrackingConfig.dwFlags = TME_LEAVE;
	MouseTrackingConfig.dwFlags = HOVER_DEFAULT;
	MouseTrackingConfig.hwndTrack = m_Handle;*/

#if HERZELEID_USE_MOUSE_DATA
	//AutoDebugVariable( StatusOnMouseTrackingRequest ) TrackMouseEvent(&MouseTrackingConfig);
#else
	if (!EnableMouseInPointer(true))
	{
		auto Error = GetLastError();
		PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : EnableMouseInPointer() failed" );
	}
	else if ( IsMouseInPointerEnabled() ) PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : EnableMouseInPointer() verified" );
#endif

	if ( m_Handle )
	{
		if ( m_Handler )
		{
			//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Setting window long" );
			// long ptr size (win32 - long, x64 - long long)
			auto pHandler = (LONG_PTR)(ICoreDispatcherMessageHandler*)(m_Handler);

			// Set the extra bytes pointer to the pWndProc object 
			// which handles messages for the window 
			SetWindowLongPtr( 
				m_Handle, /* raw window handle */ 0, /* Index */
				pHandler // dispatcher ptr
				);
		}
	
		auto hScreen = GetDC( m_Handle );
		auto iXDpi = (decltype(m_fXDpiScale))(GetDeviceCaps( hScreen, LOGPIXELSX ));
		auto iYDpi =  (decltype(m_fYDpiScale))(GetDeviceCaps( hScreen, LOGPIXELSY ));
		ReleaseDC( m_Handle, hScreen );

		m_fLogicalDpi = iXDpi;
		m_fXDpiScale = iXDpi /= (96.0f);
		m_fYDpiScale = iYDpi /= (96.0f);

		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Setting foreground window" );
		AutoDebugVariable( bStatusOnSetForeground ) SetForegroundWindow( m_Handle );
		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Setting focus on window" );
		AutoDebugVariable( bStatusOnSetFocus ) SetFocus( m_Handle );
		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Updating window" );
		AutoDebugVariable( bStatusOnUpdate ) UpdateWindow( m_Handle );
		//PrintToDebugWindowOnly8( "CoreWindow > RegisterWindow : Show cursor" );
		AutoDebugVariable( bStatusOnCursor ) ShowCursor( true );
		//AutoDebugVariable( bStatusOnCursor ) ShowCursor( false );

		if (m_bIsFullscreen)
		{
			// Initially display the window
			ShowWindow( m_Handle, SW_SHOWMAXIMIZED );
		}
		else
		{
			// Initially display the window
			ShowWindow( m_Handle, SW_SHOW );
		}
	}

}

Void CoreWindow::Activate()
{
	/*AutoDebugVariable( bStatusOnCursor ) ShowCursor( true );
	if (m_bIsFullscreen) ShowWindow( m_Handle, SW_SHOWMAXIMIZED );
	else ShowWindow( m_Handle, SW_SHOW );*/

	m_bIsInputEnabled = true;

	// The window content control is now passed 
	// to the user defined render... 
}

void CoreWindow::DestroyWindow()
{
	PrintToDebugWindowOnly8("Herzeleid. Destroying window.");
	Safe_Delete(m_Handler);

	if ( m_Handle )
	{
		//Fix the display settings if leaving full screen.
		if(m_bIsFullscreen)
		{
			ChangeDisplaySettings(NULL, 0);
		}

		::DestroyWindow( m_Handle );
		m_Handle = nullptr;

		if ( m_ModuleHandle )
		{
			UnregisterClass( 
				HerzeleidWindowClassName, 
				m_ModuleHandle 
				);

			m_ModuleHandle = null;
		}
		else
		{
			UnregisterClass( 
				HerzeleidWindowClassName, 
				GetModuleHandle(nullptr) 
				);
		}

		//Show the mouse cursor.
		ShowCursor(true);
	}
}


Void CoreWindow::Close()
{
	PostMessage(m_Handle, WM_CLOSE, 0, 0); //
	PostMessage(m_Handle, WM_DESTROY, 0, 0); 
}

CoreWindow *CoreWindow::GetForCurrentThread()
{
	return( s_pWindowForCurrentThread );
}

#pragma region CoreWindow GetKeyState Functions

#define HerzeleidAsyncKeyStateIsDown( State )		(( State & 0x8000 ) ? true : false) // most significant bit (msb, high-order bit)
#define HerzeleidAsyncKeyStateIsLocked( State )		(( State & 0x0001 ) ? true : false) // least significant bit (lsb)

CoreVirtualKeyStatesConst CoreWindow::GetAsyncKeyState( VirtualKeyConstRef Key ) const
{
	auto Status = ::GetAsyncKeyState((Int32)Key);
	if (HerzeleidAsyncKeyStateIsLocked(Status)) return( CoreVirtualKeyStates::Locked );
	else if (HerzeleidAsyncKeyStateIsDown(Status)) return( CoreVirtualKeyStates::Down);
	else return( CoreVirtualKeyStates::None );
}

CoreVirtualKeyStatesConst CoreWindow::GetKeyState( VirtualKeyConstRef Key ) const
{
	auto Status = ::GetKeyState((Int32)Key);
	if (HerzeleidAsyncKeyStateIsLocked(Status)) return( CoreVirtualKeyStates::Locked );
	else if (HerzeleidAsyncKeyStateIsDown(Status)) return( CoreVirtualKeyStates::Down);
	else return( CoreVirtualKeyStates::None );
}

#pragma endregion